#include <fs.h>
#include <status.h>
#include <log.h>
#include <mm/valloc.h>
#include <ds/lru.h>

#define FS_MAX_DEPTH 16

extern lru_t* dentry_lru, * inode_lru;

static int do_path_walk(const char* path, dentry_t* start, dentry_t** dentry, int flags, hstr_t* out, size_t depth, char* buf) {
    int errno = 0;
    int path_idx = 0;

    if (depth >= FS_MAX_DEPTH) {
        return ELOOP;
    }

    if (path[path_idx] == '/' || !start) {
        if (start) {
            start = start->sb->root;
        } else {
            start = fs_root;
            if (!start->mnt) {
                panic("No root mount point");
            }
        }
        path_idx++;
    }

    dentry_t* p_dentry;
    dentry_t* cur_d = start;
    inode_t* cur_i;

    char cur = path[path_idx++], nxt;
    hstr_t name = HSTR(buf, 0);
    while (cur) {
        nxt = path[path_idx++];
        if (cur != '/') {
            if (name.len > FS_NAME_MAX) {
                return ENAME2LONG;
            }
            if (!FS_VALID_CHAR(cur)) {
                return EINVAL;
            }
            name.val[name.len++] = cur;
            if (nxt) goto continu;
        }

        if (nxt == '/') goto continu;

        name.val[name.len] = '\0';
        hstr_rehash(&name);

        if (!nxt && (flags & FS_WALK_FDPARENT)) {
            if (out) {
                hstr_copy(out, &name);
            }
            break;
        }

        cur_i = cur_d->inode;

        lock_dentry(cur_d);

        p_dentry = fs_dcache_lookup(cur_d, &name);
        if (!p_dentry) {

            p_dentry = fs_alloc_dentry(cur_d, &name);
            if (!p_dentry) {
                errno = ENOMEM;
                goto fail;
            }

            lock_inode(cur_i);

            errno = cur_i->iops->lookup(cur_i, p_dentry);

            if (errno == ENOENT && (flags & FS_WALK_MKPARENT)) {
                if (!cur_i->iops->mkdir) {
                    errno = ENOSET;
                } else {
                    errno = cur_i->iops->mkdir(cur_i, p_dentry);
                }
            }

            fs_dcache_add(cur_d, p_dentry);
            unlock_inode(cur_i);

            if (errno) {
                unlock_dentry(cur_d);
                goto clean;
            }
        }

        unlock_dentry(cur_d);

        name.len = 0;
        cur_d = p_dentry;
    continu:
        cur = nxt;
    }
    *dentry = cur_d;
    return 0;

clean:
    fs_free_dentry(p_dentry);
fail:
    *dentry = NULL;
    return errno;
}

int fs_path_walk(const char* path, dentry_t* start, dentry_t** dentry, hstr_t* out, int flags) {
    char* name_buf = valloc(1024);
    int errno = do_path_walk(path, start, dentry, flags, out, 0, name_buf);
    vfree(name_buf);
    return errno;
}